/**
 * @file math_fun.h
 *
 * Elementwise functions on Eigen arrays
 *
 * @author Dahua Lin
 */

#ifdef _MSC_VER
#pragma once
#endif

#ifndef SLIPP_EIGEN_EFUNS_H_
#define SLIPP_EIGEN_EFUNS_H_

#include <slipp/eigen_ex/eigen_common.h>
#include <slipp/base/math_base.h>


#define SLIPP_DEFINE_EIGEN_UNARY_EFUN(func_name, functorT) template<class Derived> \
		inline Eigen::CwiseUnaryOp<functorT<typename Eigen::ArrayBase<Derived>::Scalar>, const Derived> \
		func_name(const Eigen::ArrayBase<Derived>& a) { return a.derived(); }

#define SLIPP_DEFINE_EIGEN_BINARY_EFUN(func_name, functorT) template<class Derived1, class Derived2> \
		inline Eigen::CwiseBinaryOp<functorT<typename Eigen::ArrayBase<Derived1>::Scalar>, const Derived1, const Derived2> \
		func_name(const Eigen::ArrayBase<Derived1>& a, const Eigen::ArrayBase<Derived2>& b) { \
			static_assert(is_same<typename Eigen::ArrayBase<Derived1>::Scalar, \
					typename Eigen::ArrayBase<Derived2>::Scalar>::value, \
					"Operands of a binary function must have the same scalar type."); \
			typedef functorT<typename Eigen::ArrayBase<Derived1>::Scalar> functor_t; \
			return Eigen::CwiseBinaryOp<functor_t, const Derived1, const Derived2>(a.derived(), b.derived()); }


namespace sli
{
	/**************************************************
	 *
	 *  scalar functions lacked in Eigen
	 *
	 **************************************************/

	namespace _detail
	{
		template<typename Scalar>
		struct scalar_pow_op : std::binary_function<Scalar, Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x, const Scalar& e) const { return math::pow(x, e); }
		};

		template<typename Scalar>
		struct scalar_floor_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::floor(x); }
		};

		template<typename Scalar>
		struct scalar_ceil_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::ceil(x); }
		};

		template<typename Scalar>
		struct scalar_round_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::round(x); }
		};

		template<typename Scalar>
		struct scalar_atan_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::atan(x); }
		};

		template<typename Scalar>
		struct scalar_atan2_op : std::binary_function<Scalar, Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& y, const Scalar& x) const { return math::atan2(y, x); }
		};

		template<typename Scalar>
		struct scalar_sinh_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::sinh(x); }
		};

		template<typename Scalar>
		struct scalar_cosh_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::cosh(x); }
		};

		template<typename Scalar>
		struct scalar_tanh_op : std::unary_function<Scalar, Scalar>
		{
			const Scalar operator() (const Scalar& x) const { return math::tanh(x); }
		};
	}

	/**************************************************
	 *
	 *  Generic functions
	 *
	 **************************************************/

	// power functions

	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vabs, Eigen::internal::scalar_abs_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vsqr, Eigen::internal::scalar_square_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vsqrt, Eigen::internal::scalar_sqrt_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vcube, Eigen::internal::scalar_cube_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vrcp, Eigen::internal::scalar_inverse_op)

	template<class Derived>
	inline const Eigen::CwiseUnaryOp<
	Eigen::internal::scalar_pow_op<typename Eigen::ArrayBase<Derived>::Scalar>, const Derived>
	vpow(const Eigen::ArrayBase<Derived>& a, const typename Eigen::ArrayBase<Derived>::Scalar& e)
	{
		typedef typename Eigen::ArrayBase<Derived>::Scalar scalar_t;
		return Eigen::CwiseUnaryOp<Eigen::internal::scalar_pow_op<scalar_t>, const Derived>
			  (a.derived(), Eigen::internal::scalar_pow_op<scalar_t>(e));
	}

	SLIPP_DEFINE_EIGEN_BINARY_EFUN(vpow, _detail::scalar_pow_op)

	// exponential and logarithm functions

	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vexp, Eigen::internal::scalar_exp_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vlog, Eigen::internal::scalar_log_op)

	// rounding functions

	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vfloor, _detail::scalar_floor_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vceil,  _detail::scalar_ceil_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vround,  _detail::scalar_round_op)

	// trigonometric functions

	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vsin, Eigen::internal::scalar_sin_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vcos, Eigen::internal::scalar_cos_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vtan, Eigen::internal::scalar_tan_op)

	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vasin, Eigen::internal::scalar_asin_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vacos, Eigen::internal::scalar_acos_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vatan, _detail::scalar_atan_op)
	SLIPP_DEFINE_EIGEN_BINARY_EFUN(vatan2, _detail::scalar_atan2_op)

	// hyperbolic functions

	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vsinh, _detail::scalar_sinh_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vcosh, _detail::scalar_cosh_op)
	SLIPP_DEFINE_EIGEN_UNARY_EFUN(vtanh, _detail::scalar_tanh_op)

}

#endif
